﻿using UnityEngine;
using System.Collections;
using UnityEditor;

namespace RootMotion.Dynamics {

	public enum JointAxis {
		Primary,
		Secondary,
		Tertiary
	}

	// Tools for editing Joints.
	public class JointTools: Editor {

		public static Quaternion LocalToJointSpace(ConfigurableJoint joint) {
			Vector3 forward = Vector3.Cross (joint.axis, joint.secondaryAxis).normalized;
			Vector3 up = Vector3.Cross(forward, joint.axis).normalized;

			if (forward == up) {
				Debug.LogWarning("Joint " + joint.name + " secondaryAxis is in the exact same direction as its axis. Please make sure they are not aligned.");
			}

			return Quaternion.LookRotation(forward, up);
		}

		public static Quaternion LocalToJointSpace(CharacterJoint joint) {
			Vector3 forward = Vector3.Cross (joint.axis, joint.swingAxis).normalized;
			Vector3 up = Vector3.Cross(forward, joint.axis).normalized;
			
			if (forward == up) {
				Debug.LogWarning("Joint " + joint.name + " swingAxis is in the exact same direction as its axis. Please make sure they are not aligned.");
			}
			
			return Quaternion.LookRotation(forward, up);
		}

		public static Quaternion JointToLocalSpace(ConfigurableJoint joint) {
			return Quaternion.Inverse(LocalToJointSpace(joint));
		}

		public static Quaternion JointToLocalSpace(CharacterJoint joint) {
			return Quaternion.Inverse(LocalToJointSpace(joint));
		}

		public static JointAxis GetJointAxis(ConfigurableJoint joint, Vector3 vLocal) {
			return GetJointAxis(joint.axis, joint.secondaryAxis, vLocal);
		}

		public static JointAxis GetJointAxis(CharacterJoint joint, Vector3 vLocal) {
			return GetJointAxis(joint.axis, joint.swingAxis, vLocal);
		}

		public static JointAxis GetJointAxis(Vector3 axis, Vector3 secondaryAxis, Vector3 vLocal) {
			vLocal = vLocal.normalized;
			
			float axisDot = Mathf.Abs(Vector3.Dot(axis, vLocal));
			float secondaryAxisDot = Mathf.Abs(Vector3.Dot(secondaryAxis, vLocal));
			float tertiaryAxisDot = Mathf.Abs(Vector3.Dot(Vector3.Cross(axis, secondaryAxis), vLocal));
			
			if (axisDot > secondaryAxisDot && axisDot > tertiaryAxisDot) return JointAxis.Primary;
			if (secondaryAxisDot > axisDot && secondaryAxisDot > tertiaryAxisDot) return JointAxis.Secondary;
			return JointAxis.Tertiary;
		}

		public static JointAxis GetSymmetricJointAxis(ConfigurableJoint joint, JointAxis jointAxis, ConfigurableJoint symmetricJoint, Transform root) {
			return GetSymmetricJointAxis(joint.transform, joint.axis, joint.secondaryAxis, jointAxis, symmetricJoint.transform, symmetricJoint.axis, symmetricJoint.secondaryAxis, root);
		}

		public static JointAxis GetSymmetricJointAxis(CharacterJoint joint, JointAxis jointAxis, CharacterJoint symmetricJoint, Transform root) {
			return GetSymmetricJointAxis(joint.transform, joint.axis, joint.swingAxis, jointAxis, symmetricJoint.transform, symmetricJoint.axis, symmetricJoint.swingAxis, root);
		}

		public static JointAxis GetSymmetricJointAxis(Transform jointTransform, Vector3 axis, Vector3 secondaryAxis, JointAxis jointAxis, Transform symmetricTransform, Vector3 symmetricAxis, Vector3 symmetricSecondaryAxis, Transform root) {
			Vector3 a = JointAxisToVector3(axis, secondaryAxis, jointAxis);
			Vector3 aWorld = jointTransform.rotation * a;
			
			Vector3 aWorldMirror = SymmetryTools.Mirror(aWorld, root);
			Vector3 aLocalMirror = Quaternion.Inverse(symmetricTransform.rotation) * aWorldMirror;
			
			Vector3 sAVector = AxisTools.GetAxisVectorToDirection(symmetricTransform, aWorldMirror);
			Vector3 sA = Vector3.Project(aLocalMirror, sAVector);
			
			return GetJointAxis(symmetricAxis, symmetricSecondaryAxis, sA);
		}

		public static Vector3 JointAxisToVector3(ConfigurableJoint joint, JointAxis jointAxis) {
			return JointAxisToVector3(joint.axis, joint.secondaryAxis, jointAxis);
		}

		public static Vector3 JointAxisToVector3(CharacterJoint joint, JointAxis jointAxis) {
			return JointAxisToVector3(joint.axis, joint.swingAxis, jointAxis);
		}

		public static Vector3 JointAxisToVector3(Vector3 axis, Vector3 secondaryAxis, JointAxis jointAxis) {
			switch(jointAxis) {
			case JointAxis.Primary: return axis;
			case JointAxis.Secondary: return secondaryAxis;
			default: return Vector3.Cross(axis, secondaryAxis);
			}
		}

		public static void ApplyXDeltaToJointLimit(ref ConfigurableJoint joint, float delta, JointAxis jointAxis, bool low) {
			switch(jointAxis) {
			case JointAxis.Primary:
				if (low) {
					SoftJointLimit lowX = joint.lowAngularXLimit;
					lowX.limit += delta;
					joint.lowAngularXLimit = lowX;
				} else {
					SoftJointLimit highX = joint.highAngularXLimit;
					highX.limit -= delta;
					joint.highAngularXLimit = highX;
				}
				break;
			case JointAxis.Secondary:
				SoftJointLimit y = joint.angularYLimit;
				y.limit += delta * 0.5f;
				joint.angularYLimit = y;
				break;
			case JointAxis.Tertiary:
				SoftJointLimit z = joint.angularZLimit;
				z.limit += delta * 0.5f;
				joint.angularZLimit = z;
				break;
			}
		}

		public static void ApplyXDeltaToJointLimit(ref CharacterJoint joint, float delta, JointAxis jointAxis, bool low) {
			switch(jointAxis) {
			case JointAxis.Primary:
				if (low) {
					SoftJointLimit lowX = joint.lowTwistLimit;
					lowX.limit += delta;
					joint.lowTwistLimit = lowX;
				} else {
					SoftJointLimit highX = joint.highTwistLimit;
					highX.limit -= delta;
					joint.highTwistLimit = highX;
				}
				break;
			case JointAxis.Secondary:
				SoftJointLimit y = joint.swing1Limit;
				y.limit += delta * 0.5f;
				joint.swing1Limit = y;
				break;
			case JointAxis.Tertiary:
				SoftJointLimit z = joint.swing2Limit;
				z.limit += delta * 0.5f;
				joint.swing2Limit = z;
				break;
			}
		}

		public static void ApplyDeltaToJointLimit(ref ConfigurableJoint joint, float delta, JointAxis jointAxis) {
			switch(jointAxis) {
			case JointAxis.Primary:
				SoftJointLimit lowX = joint.lowAngularXLimit;
				lowX.limit += delta;
				joint.lowAngularXLimit = lowX;

				SoftJointLimit highX = joint.highAngularXLimit;
				highX.limit += delta;
				joint.highAngularXLimit = highX;
				break;
			case JointAxis.Secondary:
				SoftJointLimit y = joint.angularYLimit;
				y.limit += delta;
				joint.angularYLimit = y;
				break;
			case JointAxis.Tertiary:
				SoftJointLimit z = joint.angularZLimit;
				z.limit += delta;
				joint.angularZLimit = z;
				break;
			}
		}

		public static void ApplyDeltaToJointLimit(ref CharacterJoint joint, float delta, JointAxis jointAxis) {
			switch(jointAxis) {
			case JointAxis.Primary:
				SoftJointLimit lowX = joint.lowTwistLimit;
				lowX.limit += delta;
				joint.lowTwistLimit = lowX;
				
				SoftJointLimit highX = joint.highTwistLimit;
				highX.limit += delta;
				joint.highTwistLimit = highX;
				break;
			case JointAxis.Secondary:
				SoftJointLimit y = joint.swing1Limit;
				y.limit += delta;
				joint.swing1Limit = y;
				break;
			case JointAxis.Tertiary:
				SoftJointLimit z = joint.swing2Limit;
				z.limit += delta;
				joint.swing2Limit = z;
				break;
			}
		}

		public static Vector3 GetLowXAxisWorld(Joint joint) {
			return joint.transform.rotation * joint.axis;
		}

		public static Vector3 GetHighXAxisWorld(Joint joint) {
			return joint.transform.rotation * -joint.axis;
		}

		public static void SwitchXY(ref ConfigurableJoint joint) {
			Undo.RecordObject(joint, "Switch Yellow/Green Joint Axis");

			Vector3 axis = joint.axis;
			joint.axis = joint.secondaryAxis;
			joint.secondaryAxis = axis;
		}

		public static void SwitchXZ(ref ConfigurableJoint joint) {
			Undo.RecordObject(joint, "Switch Yellow/Blue Joint Axis");
			
			Vector3 cross = Vector3.Cross(joint.axis, joint.secondaryAxis).normalized;
			joint.axis = cross;
		}

		public static void SwitchYZ(ref ConfigurableJoint joint) {
			Undo.RecordObject(joint, "Switch Green/Blue Joint Axis");
			
			Vector3 cross = Vector3.Cross(joint.axis, joint.secondaryAxis).normalized;
			joint.secondaryAxis = cross;
		}

		public static void SwitchXY(ref CharacterJoint joint) {
			Undo.RecordObject(joint, "Switch Yellow/Green Joint Axis");
			
			Vector3 axis = joint.axis;
			joint.axis = joint.swingAxis;
			joint.swingAxis = axis;
		}
		
		public static void SwitchXZ(ref CharacterJoint joint) {
			Undo.RecordObject(joint, "Switch Yellow/Blue Joint Axis");
			
			Vector3 cross = Vector3.Cross(joint.axis, joint.swingAxis).normalized;
			joint.axis = cross;
		}
		
		public static void SwitchYZ(ref CharacterJoint joint) {
			Undo.RecordObject(joint, "Switch Green/Blue Joint Axis");
			
			Vector3 cross = Vector3.Cross(joint.axis, joint.swingAxis).normalized;
			joint.swingAxis = cross;
		}

		public static void InvertAxis(ref Joint joint) {
			Undo.RecordObject(joint, "Invert Axis");

			joint.axis = -joint.axis;
		}

		public static void InvertSecondaryAxis(ref Joint joint) {
			if (joint is ConfigurableJoint) {
				var j = joint as ConfigurableJoint;
				Undo.RecordObject(j, "Invert Secondary Axis");
				j.secondaryAxis = -j.secondaryAxis;
			}

			if (joint is CharacterJoint) {
				var j = joint as CharacterJoint;
				Undo.RecordObject(j, "Invert Swing Axis");
				j.swingAxis = -j.swingAxis;
			}
		}

	}
}
